package com.ruoyi.system.service.impl;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.SimpleDateFormat;
import java.util.*;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.annotation.UserDataIsolation;
import com.ruoyi.common.enums.DataType;
import com.ruoyi.common.utils.DataUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.uuid.UUID;
import com.ruoyi.system.domain.*;
import com.ruoyi.system.domain.vo.QueryVo;
import com.ruoyi.system.mapper.DeviceMapper;
import com.ruoyi.system.service.*;

import org.apache.commons.collections4.map.LinkedMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import lombok.extern.slf4j.Slf4j;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Null;

@SuppressWarnings("AlibabaLowerCamelCaseVariableNaming")
@Slf4j
@Service
public class DeviceServiceImpl implements IDeviceService
{
    @Autowired
    private IThingsBoardTenantService thingsBoardTenantService;

    @Autowired
    private IProductService productService;

    @Autowired
    private IProductTerminalService productTerminalService;

    @Autowired
    private IProductTelemetryService productTelemetryService;

    @Autowired
    private IDeviceTerminalService deviceTerminalService;

    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private IDeviceTerminalMqttService deviceTerminalMqttService;
    @Autowired
    private IProductPointCheckConfigService productPointCheckConfigService;
    @Autowired
    private DevicePointCheckConfigServiceImpl devicePointCheckConfigService;

    @Autowired
    private IDeviceCustomFieldService deviceCustomFieldService;

    @Value("${device.checkConfig.path:/device/check_config.json}")
    private String checkConfigPath;

    @Override
    public Map<String, Object> parseInjectionItem(JSONObject item, Long companyId) {
        Map<String, Object> parsed = new HashMap<>();
        try {
            String productName = item.getString("产品型号");
            if (productName == null) {
                parsed.put("message", "产品型号不能为空");
                return parsed;
            }
            String name = item.getString("产品名称");
            if (name == null) {
                parsed.put("message", "产品名称不能为空");
                return parsed;
            }
            String sn = item.getString("产品代码");
            if (sn == null) {
                parsed.put("message", "产品代码不能为空");
                return parsed;
            }
            String system = item.getString("系统");
            if (system == null) {
                system = "";
            }
            String positionNumber = item.getString("位号");
            if (positionNumber == null) {
                positionNumber = "";
            }
            String description = item.getString("备注");
            if (description == null) {
                description = "";
            }
            String address = item.getString("地址");
            if (address == null) {
                address = "";
            }
            String lng = item.getString("经度");
            String lat = item.getString("纬度");
            QueryVo queryVo = new QueryVo();
            queryVo.filters.put("name", productName);
            queryVo.filters.put("company_id", companyId);
            Product product = productService.lookup(queryVo);
            if (product == null) {
                parsed.put("message", String.format("产品型号 %s 不存在", productName));
                return parsed;
            }
            List<String> terminalSnList = new ArrayList<>();
            List<Object> terminalCredentialsList = new ArrayList<>();
            List<ProductTerminal> productTerminals = product.getTerminals();
            for (int i = 0; i < productTerminals.size(); i++) {
                String snKey = String.format("终端%d序列号", i + 1);
                String terminalSn = item.getString(snKey);
                terminalSnList.add(terminalSn);
                String credentialsKey = String.format("终端%d协议配置", i + 1);
                String credentialsString = item.getString(credentialsKey);
                if (credentialsString != null) {
                    String protocol = productTerminals.get(i).getProtocol();
                    if (protocol.equals("MQTT")) {
                        List<String> pieces = Arrays.asList(credentialsString.split("[,，]"));
                        if (pieces.size() == 3) {
                            MqttCredentials mqttCredentials = new MqttCredentials();
                            mqttCredentials.setClientId(pieces.get(0));
                            mqttCredentials.setUsername(pieces.get(1));
                            mqttCredentials.setPassword(pieces.get(2));
                            terminalCredentialsList.add(mqttCredentials);
                        } else {
                            parsed.put("message", "MQTT 配置不正确");
                            return parsed;
                        }
                    } else if (protocol.equals("HTTP")) {
                        HttpCredentials httpCredentials = new HttpCredentials();
                        httpCredentials.setAccessToken(credentialsString);
                        terminalCredentialsList.add(httpCredentials);
                    } else if (protocol.equals("CoAP")) {
                        CoapCredentials coapCredentials = new CoapCredentials();
                        coapCredentials.setAccessToken(credentialsString);
                        terminalCredentialsList.add(coapCredentials);
                    } else if (protocol.equals("LwM2M")) {
                        Lwm2mCredentials lwm2mCredentials = new Lwm2mCredentials();
                        lwm2mCredentials.setAccessToken(credentialsString);
                        terminalCredentialsList.add(lwm2mCredentials);
                    } else {
                        terminalCredentialsList.add(null);
                    }
                } else {
                    terminalCredentialsList.add(null);
                }
            }
            Device device = new Device();
            device.setProductId(product.getId());
            device.setName(name);
            device.setSn(sn);
            device.setSystem(system);
            device.setPositionNumber(positionNumber);
            device.setDescription(description);
            device.setAddress(address);
            if (lng != null) {
                device.setLng(Double.parseDouble(lng));
            }
            if (lat != null) {
                device.setLat(Double.parseDouble(lat));
            }
            parsed.put("device", device);
            parsed.put("terminalSnList", terminalSnList);
            parsed.put("terminalCredentialsList", terminalCredentialsList);
        } catch (Exception e) {
            log.info("",e);
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return parsed;
    }

    @Override
    @UserDataIsolation(tableAlias = "tbl_device")
    public Map<String, Object> findExistance(Device device) {
        return deviceMapper.findExistance(device);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer create(Device device, SysCompanyThingsboard sysCompanyThingsboard) {
        Integer id = null;
        try {
            QueryVo queryVo = new QueryVo();
            queryVo.filters.put("product_id", device.getProductId());
            queryVo.filters.put("company_id", device.getCompanyId());
            List<ProductTerminal> productTerminals = productTerminalService.index(queryVo);
            String name = UUID.fastUUID().toString();
            // TODO: [ZY] take care of the synchronization situations between app and tb
            org.thingsboard.server.common.data.Device thingsBoardDevice = thingsBoardTenantService.saveDefaultDevice(name, sysCompanyThingsboard);
            String tbDeviceId = thingsBoardDevice.getId().getId().toString();
            String tbDeviceName = thingsBoardDevice.getName();
            device.setTbDeviceId(tbDeviceId);
            device.setTbDeviceName(tbDeviceName);
            Integer affected = deviceMapper.create(device);
            if (affected > 0) {
                List<DeviceTerminal> terminals = new ArrayList<>();
                id = device.getId();

                for (ProductTerminal productTerminal : productTerminals) {
                    name = UUID.fastUUID().toString();
                    if ("MQTT".equals(productTerminal.getProtocol())) {
                        thingsBoardDevice = thingsBoardTenantService.saveMqttDevice(name, sysCompanyThingsboard);
                    } else if ("HTTP".equals(productTerminal.getProtocol())) {
                        thingsBoardDevice = thingsBoardTenantService.saveHttpDevice(name, sysCompanyThingsboard);
                    } else if ("CoAP".equals(productTerminal.getProtocol())) {
                        thingsBoardDevice = thingsBoardTenantService.saveCoapDevice(name, sysCompanyThingsboard);
                    } else if ("LwM2M".equals(productTerminal.getProtocol())) {
                        thingsBoardDevice = thingsBoardTenantService.saveLwm2mDevice(name, sysCompanyThingsboard);
                    }
                    tbDeviceId = thingsBoardDevice.getId().getId().toString();
                    tbDeviceName = thingsBoardDevice.getName();
                    DeviceTerminal deviceTerminal = new DeviceTerminal();
                    deviceTerminal.setDeviceId(device.getId());
                    deviceTerminal.setCompanyId(device.getCompanyId());
                    deviceTerminal.setSn("");
                    deviceTerminal.setProductTerminalId(productTerminal.getId());
                    deviceTerminal.setTbDeviceId(tbDeviceId);
                    deviceTerminal.setTbDeviceName(tbDeviceName);
                    if (deviceTerminalService.create(deviceTerminal) == null) {
                        throw new Exception("rollback");
                    }
                    deviceTerminal.setProductTerminalNumber(productTerminal.getNumber());
                    deviceTerminal.setProductTerminalProtocol(productTerminal.getProtocol());
                    terminals.add(deviceTerminal);
                }

                //初始点检配置
                JSONArray jsonObject = checkConfig();
                for(int i=0;i<jsonObject.size();i++) {
                    Integer category =(Integer) jsonObject.getJSONObject(i).get("category");
                    String type =(String) jsonObject.getJSONObject(i).get("type");
                    String item =(String) jsonObject.getJSONObject(i).get("item");
                    if(category==0){
                        DevicePointCheckConfig config = new DevicePointCheckConfig();
                        config.setDeviceId(id);
                        config.setCategory(category);
                        config.setType(type);
                        config.setItem(item);
                        config.setTypeOrder((Integer) jsonObject.getJSONObject(i).get("typeOrder"));
                        config.setItemOrder((Integer) jsonObject.getJSONObject(i).get("itemOrder"));
                        config.setOrderNum((Integer) jsonObject.getJSONObject(i).get("orderNum"));
                        config.setCreateBy(device.getCreateBy());
                        config.setCreateTime(device.getCreateTime());
                        config.setCompanyId(device.getCompanyId());
                        devicePointCheckConfigService.add(config);
                    }else{
                        JSONArray  checkConfigs = (JSONArray) jsonObject.getJSONObject(i).get("childData");
                        for(int j=0;j<checkConfigs.size();j++) {
                            DevicePointCheckConfig config = new DevicePointCheckConfig();
                            config.setDeviceId(id);
                            config.setCategory(category);
                            config.setType(type);
                            config.setItem(item);
                            config.setField((String) checkConfigs.getJSONObject(j).get("field"));
                            config.setDataType((String) checkConfigs.getJSONObject(j).get("dataType"));
                            config.setDetail((String) checkConfigs.getJSONObject(j).get("detail"));
                            config.setTypeOrder((Integer) checkConfigs.getJSONObject(j).get("typeOrder"));
                            config.setItemOrder((Integer) checkConfigs.getJSONObject(j).get("itemOrder"));
                            config.setOrderNum((Integer) checkConfigs.getJSONObject(j).get("orderNum"));
                            config.setCreateBy(device.getCreateBy());
                            config.setCreateTime(device.getCreateTime());
                            config.setCompanyId(device.getCompanyId());
                            devicePointCheckConfigService.add(config);

                        }


                    }
                }


                //初始化记录表 表头配置
                List<Map<String, String>> fieldList = fieldConfig();
                for (Map<String, String> field : fieldList) {
                    DeviceCustomField deviceCustomField = new DeviceCustomField();
                    deviceCustomField.setDeviceId(id);
                    deviceCustomField.setName(field.get("name"));
                    if(field.get("name").equals("名称")){
                        deviceCustomField.setValue(device.getName());
                    }else{
                        deviceCustomField.setValue(device.getSn());
                    }
                    deviceCustomField.setCompanyId(device.getCompanyId());
                    deviceCustomField.setCreateBy(device.getCreateBy());
                    deviceCustomField.setCreateTime(device.getCreateTime());
                    deviceCustomFieldService.add(deviceCustomField);
                    }







                device.setTerminals(terminals);
            }
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            log.info("",e);
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
            id = null;
        }
        return id;
    }

    //重置所有产品的点检配置
    @Override
    @Transactional
    public Boolean initDeviceCheckConfig(){
        List<Device> devices = deviceMapper.quertList();
        for (Device device:devices
             ) {
            //初始点检配置
            Integer id = device.getId();
            JSONArray jsonObject = checkConfig();
            for(int i=0;i<jsonObject.size();i++) {
                Integer category =(Integer) jsonObject.getJSONObject(i).get("category");
                String type =(String) jsonObject.getJSONObject(i).get("type");
                String item =(String) jsonObject.getJSONObject(i).get("item");
                if(category==0){
                    DevicePointCheckConfig config = new DevicePointCheckConfig();
                    config.setDeviceId(id);
                    config.setCategory(category);
                    config.setType(type);
                    config.setItem(item);
                    config.setTypeOrder((Integer) jsonObject.getJSONObject(i).get("typeOrder"));
                    config.setItemOrder((Integer) jsonObject.getJSONObject(i).get("itemOrder"));
                    config.setOrderNum((Integer) jsonObject.getJSONObject(i).get("orderNum"));
                    config.setCreateBy(device.getCreateBy());
                    config.setCreateTime(device.getCreateTime());
                    config.setCompanyId(device.getCompanyId());
                    devicePointCheckConfigService.add(config);
                }else{
                    JSONArray  checkConfigs = (JSONArray) jsonObject.getJSONObject(i).get("childData");
                    for(int j=0;j<checkConfigs.size();j++) {
                        DevicePointCheckConfig config = new DevicePointCheckConfig();
                        config.setDeviceId(id);
                        config.setCategory(category);
                        config.setType(type);
                        config.setItem(item);
                        config.setField((String) checkConfigs.getJSONObject(j).get("field"));
                        config.setDataType((String) checkConfigs.getJSONObject(j).get("dataType"));
                        config.setDetail((String) checkConfigs.getJSONObject(j).get("detail"));
                        config.setTypeOrder((Integer) checkConfigs.getJSONObject(j).get("typeOrder"));
                        config.setItemOrder((Integer) checkConfigs.getJSONObject(j).get("itemOrder"));
                        config.setOrderNum((Integer) checkConfigs.getJSONObject(j).get("orderNum"));
                        config.setCreateBy(device.getCreateBy());
                        config.setCreateTime(device.getCreateTime());
                        config.setCompanyId(device.getCompanyId());
                        devicePointCheckConfigService.add(config);

                    }


                }
            }
        }

        return  true;
    }
    @Override
    @Transactional
    public Boolean initDeviceField(){
        List<Device> devices = deviceMapper.quertList();
        for (Device device:devices
             ) {
            //初始点检配置
            Integer id = device.getId();
            List<Map<String, String>> fieldList = fieldConfig();
            for (Map<String, String> field : fieldList) {
                DeviceCustomField deviceCustomField = new DeviceCustomField();
                deviceCustomField.setDeviceId(id);
                deviceCustomField.setName(field.get("name"));
                if(field.get("name").equals("名称")){
                    deviceCustomField.setValue(device.getName());
                }else{
                    deviceCustomField.setValue(device.getSn());
                }
                deviceCustomField.setCompanyId(device.getCompanyId());
                deviceCustomField.setCreateBy(device.getCreateBy());
                deviceCustomField.setCreateTime(device.getCreateTime());
                deviceCustomFieldService.add(deviceCustomField);
            }


        }

        return  true;
    }
    private List<Map<String,String>> fieldConfig() {
        List<Map<String,String>> result = new ArrayList<>();
        Map<String,String> map1 = new HashMap<>();
        map1.put("name","名称");
        result.add(map1);
        Map<String,String> map2 = new HashMap<>();
        map2.put("name","产品代码");
        result.add(map2);
        return  result;
    }
    private JSONArray checkConfig() {

        JSONArray checkConfig = null;
        try {
            String content = FileService.load(checkConfigPath, false);
            checkConfig = JSONArray.parseArray(content);
        } catch (Exception e) {
            log.info("",e);
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }


        return checkConfig;
    }

    @Override
    @UserDataIsolation(tableAlias = "tbl_device")
    public List<Device> index(QueryVo queryVo) {
        List<Device> devices = deviceMapper.index(queryVo);
        for (Device device : devices) {
            QueryVo terminalQueryVo = new QueryVo();
            terminalQueryVo.filters.put("device_id", device.getId());
            terminalQueryVo.filters.put("company_id",queryVo.filters.get("company_id"));
            device.setTerminals(deviceTerminalService.index(terminalQueryVo));
        }
        return devices;
    }

    @Override
    public List<Map<String, Object>> enumerate(QueryVo queryVo) {
        List<Map<String, Object>> devices = deviceMapper.enumerate(queryVo);
        return devices;
    }

    @Override
    @UserDataIsolation(tableAlias = "tbl_device")
    public List<Map<Integer, String>> enumerateAvailable(QueryVo queryVo) {
        List<Map<Integer, String>> devices = deviceMapper.enumerateAvailable(queryVo);
        return devices;
    }

    @Override
    @UserDataIsolation(tableAlias = "tbl_device")
    public Device retrieve(QueryVo queryVo) {
        Device device = deviceMapper.retrieve(queryVo);
        if (device != null) {
            QueryVo terminalQueryVo = new QueryVo();
            terminalQueryVo.filters.put("device_id", device.getId());
            device.setTerminals(deviceTerminalService.index(terminalQueryVo));
        }
        return device;
    }

    @Override
    @UserDataIsolation(tableAlias = "tbl_device")
    public Boolean update(Device device) {
        Boolean done = false;
        try {
            Integer affected = deviceMapper.update(device);
            if (affected > 0) {
                done = true;
            }
        } catch (Exception e) {
            log.info("",e);
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return done;
    }

    @Override
    @UserDataIsolation(tableAlias = "tbl_device")
    public Boolean discard(QueryVo queryVo) {
        Boolean done = false;
        try {
            Integer affected = deviceMapper.discard(queryVo);
            if (affected > 0) {
                done = true;
            }
        } catch (Exception e) {
            log.info("",e);
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
            done = false;
        }
        return done;
    }

    @Override
    @UserDataIsolation(tableAlias = "tbl_device")
    @Transactional(rollbackFor = Exception.class)
    public Boolean delete(QueryVo queryVo, SysCompanyThingsboard sysCompanyThingsboard) {
        Boolean done = false;
        try {
            List<String> tbDeviceIds = new ArrayList<>();
            // TODO: [ZY] take care of the synchronization situations between app and tb
            Device device = deviceMapper.retrieve(queryVo);
            Integer deviceId = device.getId();
            QueryVo subQueryVo = new QueryVo();
            subQueryVo.filters.put("device_id", deviceId);
            List<DeviceTerminal> deviceTerminals = deviceTerminalService.index(subQueryVo);
            for (DeviceTerminal deviceTerminal : deviceTerminals) {
                tbDeviceIds.add(deviceTerminal.getTbDeviceId());
                QueryVo delQueryVo = new QueryVo();
                delQueryVo.filters.put("id", deviceTerminal.getId());
                if (!deviceTerminalService.delete(delQueryVo)) {
                    throw new Exception("rollback");
                }
                //删除终端配置
                queryVo.filters.put("device_terminal_id",deviceTerminal.getId());
                deviceTerminalMqttService.delete(queryVo);
            }
            tbDeviceIds.add(device.getTbDeviceId());
            Integer affected = deviceMapper.delete(queryVo);
            if (affected > 0) {
                done = true;
                for (String tbDeviceId : tbDeviceIds) {
                    thingsBoardTenantService.deleteDevice(tbDeviceId, sysCompanyThingsboard);
                }
            }
        } catch (Exception e) {
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            log.info("",e);
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
            done = false;
        }
        return done;
    }

    @Override
    @UserDataIsolation(tableAlias = "tbl_device")
    public Integer addToProject(List<Integer> deviceIds, Integer projectId) {
        Integer affected = 0;
        try {
            affected = deviceMapper.assignToProject(deviceIds, projectId);
        } catch (Exception e) {
            log.info("",e);
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return affected;
    }

    @Override
    @UserDataIsolation(tableAlias = "tbl_device")
    public Integer removeFromProject(List<Integer> deviceIds) {
        Integer affected = 0;
        try {
            affected = deviceMapper.assignToProject(deviceIds, null);
        } catch (Exception e) {
            log.info("",e);
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return affected;
    }

    @Override
    public JSONObject connectivity(Device device, SysCompanyThingsboard sysCompanyThingsboard) {
        JSONObject connectivity = new JSONObject();
        List<DeviceTerminal> deviceTerminals = device.getTerminals();
        for (DeviceTerminal deviceTerminal : deviceTerminals) {
            String tbDeviceId = deviceTerminal.getTbDeviceId();
            JSONObject subConnectivity = thingsBoardTenantService.getConnectivity(tbDeviceId, sysCompanyThingsboard);
            connectivity.put(deviceTerminal.getProductTerminalName(), subConnectivity);
        }
        return connectivity;
    }

    private Double cutValue(String valueString, String stepString) {
        BigDecimal value = new BigDecimal(valueString);
        if (!stepString.isBlank()) {
            BigDecimal step = new BigDecimal(stepString);
            Integer scale = step.scale();
            value = value.divide(step).setScale(scale, RoundingMode.HALF_EVEN).multiply(step).setScale(scale, RoundingMode.HALF_EVEN);
        }
        return value.doubleValue();
    }

    private Object convertValue(String valueString, String stepString, DataType dataType) {
        Object value = null;
        if (dataType.equals(DataType.BOOL)) {
            value = Boolean.parseBoolean(valueString);
        } else if (dataType.equals(DataType.STRING)) {
            value = valueString;
        } else {
            Double doubleValue = cutValue(valueString, stepString);
            if (dataType.equals(DataType.INT32)) {
                value = doubleValue.intValue();
            } else if (dataType.equals(DataType.INT64)) {
                value = doubleValue.longValue();
            } else  {
                value = doubleValue;
            }
        }
        return value;
    }

    @Override
    public JSONObject latestTimeSeries(Device device, SysCompanyThingsboard sysCompanyThingsboard) {
        String tbDeviceId = device.getTbDeviceId();
        JSONObject timeSeries = thingsBoardTenantService.getLatestTimeSeries(tbDeviceId, sysCompanyThingsboard);
        log.debug("{}", timeSeries);
        QueryVo queryVo = new QueryVo();
        queryVo.filters.put("product_id", device.getProductId());
        queryVo.filters.put("company_id", sysCompanyThingsboard.getCompanyId());
        List<ProductTelemetry> productTelemetries = productTelemetryService.index(queryVo);
        for (ProductTelemetry productTelemetry : productTelemetries) {
            String userIdentifier = productTelemetry.getUserIdentifier();
            JSONObject item = timeSeries.getJSONObject(userIdentifier);
            if (item == null) {
                continue;
            }
            DataType dataType = DataType.valueOfLabel(productTelemetry.getDataType());
            String valueString = item.get("value").toString();
            if (valueString == null || valueString.isBlank()) {
                continue;
            }
            try {
                item.put("value", convertValue(valueString, productTelemetry.getStep(), dataType));
            } catch (Exception e) {
                log.info("",e);
                StackTraceElement[] traces = e.getStackTrace();
                for (StackTraceElement trace : traces) {
                    log.debug(trace.toString());
                }
            }
            item.put("name", productTelemetry.getName());
            item.put("unit", productTelemetry.getUnit());
            item.put("identifier", productTelemetry.getUserIdentifier());
            item.put("isComputed", productTelemetry.getIsComputed());
            item.put("formulaImage", productTelemetry.getFormulaImage());
        }
        return timeSeries;
    }

    @Override
    public JSONObject timeSeriesMeta(Device device, SysCompanyThingsboard sysCompanyThingsboard) {
        String tbDeviceId = device.getTbDeviceId();
        JSONObject meta = new JSONObject();
        QueryVo queryVo = new QueryVo();
        queryVo.filters.put("product_id", device.getProductId());
        List<Map<String, Object>> telemetries = productTelemetryService.enumerate(queryVo);
        if (telemetries != null && !telemetries.isEmpty()) {
            JSONArray identifiers = new JSONArray();
            for (Map<String, Object> telemetry : telemetries) {
                identifiers.add(telemetry.get("user_identifier"));
            }
            queryVo.filters.put("identifiers", identifiers);
            meta = thingsBoardTenantService.getTimeSeriesMeta(tbDeviceId, queryVo, sysCompanyThingsboard);
        }
        return meta;
    }

    @Override
    public JSONArray historicalTimeSeries(Device device, QueryVo queryVo, SysCompanyThingsboard sysCompanyThingsboard) {
        JSONArray timeSeries = new JSONArray();
        String tbDeviceId = device.getTbDeviceId();
        JSONObject raw = thingsBoardTenantService.getHistoricalTimeSeries(tbDeviceId, queryVo, sysCompanyThingsboard);
        QueryVo subQueryVo = new QueryVo();
        subQueryVo.filters.put("product_id", device.getProductId());
        queryVo.filters.put("company_id", sysCompanyThingsboard.getCompanyId());
        List<ProductTelemetry> productTelemetries = productTelemetryService.index(queryVo);
        Map<String, ProductTelemetry> indexed = new LinkedMap<>();
        for (ProductTelemetry productTelemetry : productTelemetries) {
            indexed.put(productTelemetry.getUserIdentifier(), productTelemetry);
        }
        List<String> tsList = new ArrayList<>(raw.keySet());
        Collections.sort(tsList);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        for (String tsString : tsList) {
            Long ts = Long.parseLong(tsString);
            Date time = new Date();
            time.setTime(ts);
            JSONObject entry = new JSONObject();
            entry.put("time", sdf.format(time));
            JSONObject values = raw.getJSONObject(tsString);
            List<String> keys = new ArrayList<>(values.keySet());
            Collections.sort(keys);
            for (String key : keys) {
                if (key.startsWith("__active__")) {
                    entry.put(key, values.getBoolean(key) ? 1 : 0);
                    continue;
                }
                ProductTelemetry productTelemetry = indexed.get(key);
                DataType dataType = DataType.valueOfLabel(productTelemetry.getDataType());
                Object value = convertValue(values.get(key).toString(), productTelemetry.getStep(), dataType);
                entry.put(key, value);
            }
            timeSeries.add(entry);
        }
        return timeSeries;
    }

    @Override
    public List<Map<String, Object>> summary(QueryVo queryVo, Map<String, Object> options) {
        List<Map<String, Object>> summary = null;
        try {
            Map<String, Object> summaryKV = deviceMapper.summary(queryVo);
            summary = DataUtils.KVtoList(summaryKV, options);
        } catch (Exception e) {
            log.info("",e);
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return summary;
    }

    @Override
    public List<Map<String, Object>> provinceSummary(QueryVo queryVo) {
        List<Map<String, Object>> provinces = deviceMapper.provinceSummary(queryVo);
        for (Map<String, Object> province : provinces) {
            String provinceCode = (String)province.get("province_code");
            if (provinceCode != null) {
                province.put("province", ChinaAreaDataService.codeToName(provinceCode));
            } else {
                province.put("province", "未设置");
            }
        }
        return provinces;
    }

    @Override
    public List<Map<String, Object>> deviceAndTerminalCountByMonth(QueryVo queryVo) {
        List<Map<String, Object>> result = new ArrayList<>();
        try {
            Set<String> months = null;
            Date start = queryVo.filters.getDate("start");
            if (start != null) {
                months = new TreeSet<>();
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM");
                Calendar calendar = Calendar.getInstance();
                calendar.setTime(start);
                Date now = new Date();
                while (calendar.getTime().before(now)) {
                    months.add(sdf.format(calendar.getTime()));
                    calendar.add(Calendar.MONTH, 1);
                }
            }
            List<Map<String, Object>> monthValues = deviceMapper.deviceAndTerminalCountByMonth(queryVo);
            Map<String, Map<String, Integer>> monthMap = new LinkedMap<>();
            for (Map<String, Object> item : monthValues) {
                String month = item.get("month").toString();
                Map<String, Integer> counts = monthMap.get(month);
                if (counts == null) {
                    counts = new LinkedMap<>();
                    counts.put("device", 0);
                    counts.put("terminal", 0);
                    monthMap.put(month, counts);
                }
                Integer deviceCount = Integer.parseInt(item.get("device_count").toString());
                Integer terminalCount = Integer.parseInt(item.get("terminal_count").toString());
                counts.put("device", deviceCount);
                counts.put("terminal", terminalCount);
            }
            if (months == null) {
                months = monthMap.keySet();
            }
            for (String month : months) {
                Map<String, Integer> counts = monthMap.get(month);
                Map<String, Object> resultItem = new LinkedMap<>();
                resultItem.put("月份", month);
                if (counts == null) {
                    resultItem.put("产品", 0);
                    resultItem.put("传感器", 0);
                } else {
                    resultItem.put("产品", counts.get("device"));
                    resultItem.put("传感器", counts.get("terminal"));
                }
                result.add(resultItem);
            }
        } catch (Exception e) {
            log.info("",e);
            StackTraceElement[] traces = e.getStackTrace();
            for (StackTraceElement trace : traces) {
                log.debug(trace.toString());
            }
        }
        return result;
    }



}
